﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Babylon.Toolbox
{
    public class ModelMesh
    {
        int vertexBufferID = -1;
        int indexBufferID = -1;
        int verticesOffset;
        int indicesOffset;

        readonly List<ModelMeshPart> parts = new List<ModelMeshPart>();

        Vector3[] _positions;
        int[] _indices;

        public int BoneIndex { get; private set; }
        public Bone Bone { get { return Model.Bones[BoneIndex]; } }
        public ReadOnlyCollection<ModelMeshPart> Parts { get; private set; }

        public ModelMesh(int boneIndex = 0)
        {
            Parts = new ReadOnlyCollection<ModelMeshPart>(parts);
            BoneIndex = boneIndex;
        }

        public IEnumerable<ModelEffect> Effects
        {
            get
            {
                return Parts.Where(part => part.Effect != null).Select(part => part.Effect);
            }
        }

        public VertexBuffer VertexBuffer
        {
            get
            {
                if (vertexBufferID == -1)
                    throw new Exception(Strings.VertexBufferNotSet);

                return Model.VertexBuffers[vertexBufferID];
            }
        }

        public IndexBuffer IndexBuffer{
            get{
                if(indexBufferID == -1)
                    throw new Exception(Strings.IndexBufferNotSet);

                return Model.IndexBuffers[indexBufferID];
            }
        }

        public int VerticesCount { get; internal set; }

        public GraphicsDevice Device
        {
            get { return Model.Device; }
        }

        public Model Model
        {
            get;
            internal set;
        }

        internal int VertexBufferId
        {
            set { vertexBufferID = value; }
        }

        internal int VerticesOffset
        {
            get { return verticesOffset; }
            set { verticesOffset = value; }
        }

        internal int IndicesOffset
        {
            get { return indicesOffset; }
            set { indicesOffset = value; }
        }

        internal int IndexBufferId{
            set { indexBufferID = value; }
        }

        public void SetBuffers(int vertexBufferId, int indexBufferId, int verticesOffset, int indicesOffset, int verticesCount){
            IndexBufferId = indexBufferId;
            VertexBufferId = vertexBufferId;
            VerticesOffset = verticesOffset;
            IndicesOffset = indicesOffset;
            VerticesCount = verticesCount;
        }

        public BoundingSphere BoundingSphere { get;  set; }

        public void Draw()
        {
            if (vertexBufferID == -1)
                throw new Exception(Strings.VertexBufferNotSet);

            foreach (ModelMeshPart part in Parts)
            {
                part.Draw();
            }
        }

        internal void AddPart(ModelMeshPart part)
        {
            parts.Add(part);
        }

#region Picking
        internal void SetPickingData(Vector3[] positions, int[] indices)
        {
            _positions = positions;
            _indices = indices;
        }

        public bool Intersects(Ray ray, out float distance)
        {
            if (_positions == null)
                throw new Exception(Strings.PickingInformationsAreMissing);

            distance = float.MaxValue;

            for (int index = 0; index < _indices.Length; index += 3)
            {
                Vector3 p0 = _positions[_indices[index]];
                Vector3 p1 = _positions[_indices[index + 1]];
                Vector3 p2 = _positions[_indices[index + 2]];

                float localDistance;
                if (IntersectsFace(ray, p0, p1, p2, out localDistance))
                {
                    if (localDistance < distance)
                        distance = localDistance;
                }
            }

            if (distance != float.MaxValue)
                return true;

            return false;
        }

        static bool IntersectsFace(Ray ray, Vector3 p0, Vector3 p1, Vector3 p2, out float distance)
        {
            Vector3 edge1 = p1 - p0;
            Vector3 edge2 = p2 - p0;
            Vector3 pvec = Vector3.Cross(ray.Direction, edge2);
            float det = Vector3.Dot(edge1, pvec);

            if (det == 0)
            {
                distance = 0;
                return false;
            }

            float invdet = 1f / det;

            Vector3 tvec = ray.Position - p0;

            float bu = Vector3.Dot(tvec, pvec) * invdet;

            if (bu < 0 || bu > 1.0f)
            {
                distance = 0;
                return false;
            }

            Vector3 qvec = Vector3.Cross(tvec, edge1);

            float bv = Vector3.Dot(ray.Direction, qvec) * invdet;

            if (bv < 0 || bu + bv > 1.0f)
            {
                distance = 0;
                return false;
            }

            distance = Vector3.Dot(edge2, qvec) * invdet;

            return true;
        }
#endregion
    }
}
